import threading
import math
from multiprocessing import Lock
from time import time


class SnowFlake:

    EPOCH_OFFSET = 1546272000000
    SIGN_BITS = 1
    TIMESTAMP_BITS = 41
    MACHINE_ID_BITS = 6
    DATACENTER_BITS = 6
    SEQUENCE_BITS = 10

    __instance_lock = threading.Lock()
    __instance = None
    machine_id = None
    lastTimestamp = None

    sequence = 1
    signLeftShift = TIMESTAMP_BITS + DATACENTER_BITS + MACHINE_ID_BITS + SEQUENCE_BITS
    timestampLeftShift = DATACENTER_BITS + MACHINE_ID_BITS + SEQUENCE_BITS
    machineLeftShift = MACHINE_ID_BITS + SEQUENCE_BITS
    dataCenterLeftShift = SEQUENCE_BITS
    maxTimestamp = -1 ^ (-1 << TIMESTAMP_BITS)
    maxSequenceId = -1 ^ (-1 << SEQUENCE_BITS)
    maxMachineId = -1 ^ (-1 << MACHINE_ID_BITS)
    maxDataCenterId = -1 ^ (-1 << DATACENTER_BITS)

    def __new__(cls, *args, **kwargs):
        if SnowFlake.__instance is None:
            with SnowFlake.__instance_lock:
                if SnowFlake.__instance is None:
                    SnowFlake.__instance = object.__new__(cls, *args, **kwargs)
        return SnowFlake.__instance

    def __init__(self, *args, **kwargs):
        self.lock = Lock()
        self.machine_id = 2

    def generateID(self, data_center_id=1):
        self.lock.acquire()
        if data_center_id > self.maxDataCenterId:
            raise Exception('dataCenter id should between 0 and %s' % self.maxDataCenterId)
        timestamp = self.getUnixTimestamp()
        if self.lastTimestamp and timestamp < self.lastTimestamp:
            raise Exception('Clock moved backwards!')

        if timestamp == self.lastTimestamp:
            self.sequence = self.sequence + 1
            if self.sequence == self.maxSequenceId:
                timestamp = self.getUnixTimestamp()
                while timestamp <= self.lastTimestamp:
                    timestamp = self.getUnixTimestamp()
                self.sequence = 1
        else:
            self.sequence = 1

        self.lastTimestamp = timestamp

        uuid = ((timestamp - self.EPOCH_OFFSET) << self.timestampLeftShift
                | self.machine_id << self.machineLeftShift
                | data_center_id << self.dataCenterLeftShift
                | self.sequence)

        self.lock.release()

        return uuid


    def getUnixTimestamp(self):
        return math.floor(time() * 1000)

    def parseID(self, uuid):
        data = {}
        data['sign'] = uuid >> self.signLeftShift
        data['time'] = uuid >> self.timestampLeftShift & self.maxTimestamp
        data['time'] = (data['time'] + self.EPOCH_OFFSET) / 1000
        data['machine_id'] = uuid >> self.machineLeftShift & self.maxMachineId
        data['data_center_id'] = uuid >> self.dataCenterLeftShift & self.maxDataCenterId
        data['sequence'] = uuid & self.maxSequenceId
        return data


def get_uuid(data_center_id=1):
    return SnowFlake().generateID(data_center_id)

def parse_uuid(uuid):
    return SnowFlake().parseID(uuid)


if __name__ == '__main__':
    for i in range(10):
        s = SnowFlake()
        print(id(s), s.generateID())

